//****************************************************************************
// Copyright (C) 2001-2004  PEAK System-Technik GmbH
//
// linux@peak-system.com
// www.peak-system.com
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
// Contributions: Marcel Offermans (marcel.offermans@luminis.nl)
//                Arno (a.vdlaan@hccnet.nl)
//****************************************************************************

//****************************************************************************
//
// pcan_fops.c - all file operation functions, exports only struct fops
//
// $Log: pcan_fops.c,v $
// Revision 1.46  2004/12/16 21:00:36  klaus
// experimental "... context at in_atomic() ..." bug fix, release_20041216
//
// Revision 1.45  2004/09/14 17:52:29  klaus
// Improved extended status to reflect the real content of the write queues
//
// Revision 1.44  2004/08/15 09:45:40  klaus
// added PCAN_GET_EXT_STATUS
//
// Revision 1.43  2004/07/18 14:16:16  klaus
// accept or transmitt DLC while ignoring data content when RTR data
//
// Revision 1.42  2004/05/06 19:08:13  klaus
// Improvements triggered by Mandrake distribution 2.6.3-7mdk: MODULE_VERSION macro
//
// Revision 1.41  2004/05/06 18:48:10  klaus
// Improvements triggered by Mandrake distribution 2.6.3-7mdk: MODULE_VERSION macro
//
// Revision 1.40  2004/04/18 17:59:13  klaus
// added MODULE_VERSION support for kernel 2.6.x
//
// Revision 1.39  2004/04/12 09:48:37  klaus
// Release_20040411_x, improved handling of RTR writes
//
// Revision 1.38  2004/04/11 22:03:29  klaus
// cosmetic changes
//
// Revision 1.37  2004/04/10 12:25:39  klaus
// merge polished between HEAD and kernel-2.6 branch
//
// Revision 1.36  2004/04/10 08:57:26  klaus
// merge finished between HEAD and kernel-2.6 branch
//
// Revision 1.33.2.1  2004/03/21 12:09:09  klaus
// first commit for branch to kernel 2.6 code
//
// Revision 1.35  2004/03/27 16:57:06  klaus
// modified for use with kernels <= 2.2.14
//
// Revision 1.34  2004/03/27 15:10:54  klaus
// prepared for use with gcc 3.x, modified for use with kernels < 2.2.4
//
// Revision 1.33  2003/10/12 14:37:12  klaus
// MINOR(x) becomes minor(x), previous versions minded
//
// Revision 1.32  2003/06/04 19:26:15  klaus
// adapted to kernel 2.5.69 using GCC 3.2.3 (marcel), released release_20030604_x
//
// Revision 1.30  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.29  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.28.2.27  2003/02/25 20:25:02  klaus
// correction for core dump in 'receivetest -t=pci'
//
// Revision 1.28.2.26  2003/02/23 19:33:06  klaus
// tried to fix "not possible to stop" bug
//
// Revision 1.28.2.25  2003/02/21 23:26:55  klaus
// obfuscation for pcan_usb_kernel.c improved
//
// Revision 1.28.2.24  2003/02/16 16:36:16  klaus
// pcan_usb_kernel.c returned to main modules
//
// Revision 1.28.2.23  2003/02/08 17:32:43  klaus
// modified to use pcan_usb_kernel as proprietary module
//
// Revision 1.28.2.22  2003/02/05 23:12:19  klaus
// adapted to RedHat 7.2
//
// Revision 1.28.2.21  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.28.2.20  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.28.2.19  2003/01/28 23:28:26  klaus
// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
//
//****************************************************************************

//****************************************************************************
// INCLUDES
#include <src/pcan_common.h>// must always be the 1st include
#include <linux/autoconf.h>
#include <linux/module.h>

#include <linux/kernel.h>   // DPRINTK()
#include <linux/slab.h>     // kmalloc()
#include <linux/fs.h>       // everything...
#include <linux/errno.h>    // error codes
#include <linux/types.h>    // size_t
#include <linux/proc_fs.h>  // proc 
#include <linux/fcntl.h>    // O_ACCMODE
#include <linux/pci.h>      // all about pci
#include <linux/capability.h> // all about restrictions
#include <asm/system.h>     // cli(), *_flags
#include <asm/uaccess.h>    // copy_...
#include <linux/delay.h>    // mdelay()
#include <linux/poll.h>     // poll() and select()
#include <linux/sched.h>	// missing TASK_INTERRUPTIBLE

#include <pcan.h>
#include <src/pcan_main.h>
#include <src/pcan_pci.h>
#include <src/pcan_isa.h>
#include <src/pcan_dongle.h>
#include <src/pcan_sja1000.h>
#include <src/pcan_fifo.h>
#include <src/pcan_fops.h>
#include <src/pcan_parse.h>
#include <src/pcan_usb.h>

//****************************************************************************
// DEFINES
// this was'nt present before
#ifndef MODULE_LICENSE
#define MODULE_LICENSE(x)
#endif
#ifndef MODULE_VERSION
#define MODULE_VERSION(x)
#endif

MODULE_AUTHOR("klaus.hitschler@gmx.de");
MODULE_DESCRIPTION("Driver for PEAK-Systems CAN interfaces.");
MODULE_VERSION(CURRENT_RELEASE);

#ifdef USB_SUPPORT
MODULE_SUPPORTED_DEVICE("PCAN-ISA, PCAN-PC/104, PCAN-Dongle, PCAN-PCI, PCAN-USB");
#else
MODULE_SUPPORTED_DEVICE("PCAN-ISA, PCAN-PC/104, PCAN-Dongle, PCAN-PCI");
#endif
MODULE_LICENSE("GPL");


#if defined(module_param_array) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)
extern char   *type[8];
extern ushort io[8];
extern char   irq[8];
extern char   *assign;

module_param_array(type, charp,  NULL, 0444);
module_param_array(io,   ushort, NULL, 0444);
module_param_array(irq,  byte,   NULL, 0444);
#else
MODULE_PARM(type,    "0-8s");
MODULE_PARM(io,      "0-8h");
MODULE_PARM(irq,     "0-8b");
#endif

MODULE_PARM_DESC(type,    "The type of PCAN interface (isa, sp, epp)");
MODULE_PARM_DESC(io,      "The io-port address for either PCAN-ISA, PC/104 or Dongle");
MODULE_PARM_DESC(irq,     "The interrupt number for either PCAN-ISA, PC/104 or Dongle");

// wait this time in msec at max after releasing the device - give fifo a chance to flush
#define MAX_WAIT_UNTIL_CLOSE 1000

//****************************************************************************
// GLOBALS
#if defined(LINUX_22) || defined(LINUX_24)
EXPORT_NO_SYMBOLS;
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18) || LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#define minor(x) MINOR(x)
#endif

//****************************************************************************
// LOCALS

//****************************************************************************
// CODE  

//----------------------------------------------------------------------------
// wait until write fifo is empty, max time in msec
static void wait_until_fifo_empty(struct pcandev *dev, u32 mTime)
{
  u32 dwStart = get_mtime();
  
  while (!atomic_read(&dev->DataSendReady) && ((get_mtime() - dwStart) < mTime)) 
    schedule();
  
  // force it  
  atomic_set(&dev->DataSendReady, 1);
} 


//----------------------------------------------------------------------------
// is called when the path is opened
static int pcan_open(struct inode *inode, struct file *filep)
{
  struct pcandev *dev  = (struct pcandev *)NULL; 
  struct fileobj *fobj = (struct fileobj *)NULL;
  struct list_head *ptr;
  int    err = 0;

  int _minor = minor(inode->i_rdev);

  DPRINTK(KERN_DEBUG "%s: pcan_open(), minor = %d.\n", DEVICE_NAME, _minor);

  // no devices in list
  err = -ENODEV;
  if (list_empty(&pcan_drv.devices))
  {
    DPRINTK(KERN_DEBUG "%s: no devices to select from!\n", DEVICE_NAME);
    goto out;
  }
    
  // loop trough my devices
  for (ptr = pcan_drv.devices.next; ptr != &pcan_drv.devices; ptr = ptr->next)
  {
    dev = (struct pcandev *)ptr;  
    
    if (dev->nMinor == _minor)
      break;
  }
  
  // didn't find my minor
  err = -ENODEV;
  if (dev->nMinor != _minor)
  {
    DPRINTK(KERN_DEBUG "%s: didn't find my minor!\n", DEVICE_NAME);
    goto out;
  }

  // create file object
  err = -ENOMEM;
  fobj = kmalloc(sizeof(struct fileobj), GFP_KERNEL);
  if (!fobj)
  {
    DPRINTK(KERN_DEBUG "%s: can't allocate kernel memory!\n", DEVICE_NAME);
    goto out;
  }
    
  // fill file object and init read and write method buffers
  fobj->dev = dev;
  if (filep->f_mode & FMODE_READ)
  {
    fobj->nReadRest = 0;
    fobj->nTotalReadCount = 0;
    fobj->pcReadPointer = fobj->pcReadBuffer;
  }
  
  if (filep->f_mode & FMODE_WRITE)
  {
    fobj->nWriteCount = 0;
    fobj->pcWritePointer = fobj->pcWriteBuffer;
  }

  filep->private_data = (void *)fobj;
  
  // only the first open to this device makes a default init on this device
  if (!dev->nOpenPaths)
  {
    // empty all FIFOs
		err = pcan_fifo_reset(&dev->writeFifo);
		if (err)
		  goto out;		  
		err = pcan_fifo_reset(&dev->readFifo);
		if (err)
		  goto out;
			
	  // open the interface special parts
    err = dev->open(dev);
    if (err)
    {
      DPRINTK(KERN_DEBUG "%s: can't open interface special parts!\n", DEVICE_NAME);
      goto out;
    }
  
    // special handling: probe here only for dongle devices, connect after init is possible
    if ((dev->wType == HW_DONGLE_SJA) || (dev->wType == HW_DONGLE_SJA_EPP))
    {
      err = sja1000_probe(dev); // no usb here, generic sja1000 call for dongle
      if (err)
      {
        printk(KERN_ERR "%s: %s-dongle device minor %d not found (io=0x%04x,irq=%d)\n", DEVICE_NAME,
                                  dev->type, dev->nMinor, dev->port.dng.dwPort, dev->port.dng.wIrq);
        dev->release(dev);
        goto out;
      }
    }  
 
    // install irq    
    err = dev->req_irq(dev);
    if (err)
    {
      DPRINTK(KERN_DEBUG "%s: can't request irq from device!\n", DEVICE_NAME);
      goto out;
    }

    // open the device itself
    err = dev->device_open(dev, dev->wBTR0BTR1, dev->ucCANMsgType, dev->ucListenOnly);
    if (err)
    {
      DPRINTK(KERN_DEBUG "%s: can't open device hardware itself!\n", DEVICE_NAME);
      goto out;
    }
  }

  dev->nOpenPaths++;

  DPRINTK(KERN_DEBUG "%s: pcan_open() is OK\n", DEVICE_NAME);

  return 0;

  out:

  if (fobj)
    kfree(fobj);

  return err;
}

//----------------------------------------------------------------------------
// is called when the path is closed
static int pcan_release(struct inode *inode, struct file *filep)
{
  struct fileobj *fobj = (struct fileobj *)filep->private_data;
  struct pcandev *dev;

  DPRINTK(KERN_DEBUG "%s: pcan_release()\n", DEVICE_NAME);

  // free the associated irq and allocated memory
  if (fobj && fobj->dev)
  {
    dev = fobj->dev;

    // if it's the last release: init the chip for non-intrusive operation
    if (dev->nOpenPaths > 1)
      dev->nOpenPaths--;
    else
    {
      // wait until fifo is empty or MAX_WAIT_UNTIL_CLOSE time is elapsed
      wait_until_fifo_empty(dev, MAX_WAIT_UNTIL_CLOSE);

      // release the device itself
      dev->device_release(dev);

      dev->release(dev);
      dev->nOpenPaths = 0;

      // release the interface depended irq, after this 'dev' is not valid
      dev->free_irq(fobj->dev);
    }

    kfree(fobj);
  }

  return 0;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_READ_MSG
static int pcan_ioctl_read(struct file *filep, struct pcandev *dev, TPCANRdMsg *usr)
{
  int err = 0;
  TPCANRdMsg tmp;
  TPCANRdMsg *m;

  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_READ_MSG)\n", DEVICE_NAME);
  
  // support nonblocking read if requested
  if ((filep->f_flags & O_NONBLOCK) && (!pcan_fifo_empty(&dev->readFifo)))
    return -EAGAIN;

  // sleep until data are available
  err = wait_event_interruptible(dev->read_queue, (pcan_fifo_empty(&dev->readFifo)));
  if (err)
		goto fail;

  // if the device is plugged out
  if (!dev->ucPhysicallyInstalled)
    return -ENODEV;

  // get new data out of fifo
	err = pcan_fifo_claim_for_get(&dev->readFifo, (void *)&m);
	if (err)
		goto fail;
        
  // get telegram to temp buffer
  memcpy(&tmp, m, sizeof(tmp));
		
	// free fifo
	err = pcan_fifo_get(&dev->readFifo);		    

	// signal a pending status
	if (dev->wCANStatus)
		tmp.Msg.MSGTYPE |= MSGTYPE_STATUS;

  if (copy_to_user(usr, &tmp, sizeof(*usr)))
		err = -EFAULT;
			
  fail:
  return err;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_WRITE_MSG 
static int pcan_ioctl_write(struct file *filep, struct pcandev *dev, TPCANMsg *usr)
{
  int err = 0;
  TPCANMsg tmp;
  TPCANMsg *m;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_WRITE_MSG)\n", DEVICE_NAME);

  // support nonblocking write if requested
  if ((filep->f_flags & O_NONBLOCK) && (!pcan_fifo_near_full(&dev->writeFifo)) && (!atomic_read(&dev->DataSendReady)))
    return -EAGAIN;

  // sleep until space is available
  err = wait_event_interruptible(dev->write_queue, 
	           (pcan_fifo_near_full(&dev->writeFifo) || atomic_read(&dev->DataSendReady)));
  if (err)
		goto fail;

  // if the device is plugged out
  if (!dev->ucPhysicallyInstalled)
    return -ENODEV;

  // get from user space
	if (copy_from_user(&tmp, usr, sizeof(tmp)))
	{
		err = -EFAULT;
		goto fail;
	}
	
	// filter extended data if initialized to standard only
	if (!(dev->bExtended) && ((tmp.MSGTYPE & MSGTYPE_EXTENDED) || (tmp.ID > 2047)))
	{
		err = -EINVAL;
		goto fail;
	}
    
	// put message into write FIFO
	err = pcan_fifo_claim_for_put(&dev->writeFifo, (void *)&m);
	if (err)
		goto fail;
       
  // put telegram into allocated *m       
  memcpy(m, &tmp, sizeof(*m));

	// free fifo
	err = pcan_fifo_put(&dev->writeFifo);
	if (err)
		goto fail;
		
	// pull new transmission
	if (atomic_read(&dev->DataSendReady))
	{
  	atomic_set(&dev->DataSendReady, 0);
		if ((err = dev->device_write(dev)))
		{
  		atomic_set(&dev->DataSendReady, 1);
			goto fail;
		}
	}
	else
	{
		// DPRINTK(KERN_DEBUG "%s: pushed %d item into Fifo\n", DEVICE_NAME, dev->writeFifo.nStored);
  }
		
  fail:
  return err;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_GET_EXT_STATUS 
static int pcan_ioctl_extended_status(struct pcandev *dev, TPEXTENDEDSTATUS *status)
{
  int err = 0;
 	TPEXTENDEDSTATUS local;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_GET_EXT_STATUS)\n", DEVICE_NAME);

	local.wErrorFlag = dev->wCANStatus;
	
	local.nPendingReads = dev->readFifo.nStored;
	
	// get infos for friends of polling operation
  if (!pcan_fifo_empty(&dev->readFifo))
    local.wErrorFlag |= CAN_ERR_QRCVEMPTY;
    
	local.nPendingWrites = (dev->writeFifo.nStored + ((atomic_read(&dev->DataSendReady)) ? 0 : 1));
	
  if (!pcan_fifo_near_full(&dev->writeFifo))
  	local.wErrorFlag |= CAN_ERR_QXMTFULL;
    
	local.nLastError = dev->nLastError;
	
	if (copy_to_user(status, &local, sizeof(local)))
	{
		err = -EFAULT;
		goto fail;
	}

	dev->wCANStatus = 0;
	dev->nLastError = 0;
	
	fail:
	return err;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_GET_STATUS 
static int pcan_ioctl_status(struct pcandev *dev, TPSTATUS *status)
{
  int err = 0;
 	TPSTATUS local;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_GET_STATUS)\n", DEVICE_NAME);

	local.wErrorFlag = dev->wCANStatus;
	
	// get infos for friends of polling operation
  if (!pcan_fifo_empty(&dev->readFifo))
    local.wErrorFlag |= CAN_ERR_QRCVEMPTY;
    
  if (!pcan_fifo_near_full(&dev->writeFifo))
  	local.wErrorFlag |= CAN_ERR_QXMTFULL;
    
	local.nLastError = dev->nLastError;
	
	if (copy_to_user(status, &local, sizeof(local)))
	{
		err = -EFAULT;
		goto fail;
	}

	dev->wCANStatus = 0;
	dev->nLastError = 0;
	
	fail:
	return err;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_DIAG 
static int pcan_ioctl_diag(struct pcandev *dev, TPDIAG *diag)
{
  int err = 0;
 	TPDIAG local;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_DIAG)\n", DEVICE_NAME);

	local.wType           = dev->wType;
	switch (dev->wType)
	{
	  case HW_ISA_SJA:
		  local.dwBase      = dev->port.isa.dwPort;
      local.wIrqLevel   = dev->port.isa.wIrq;
		  break;
		case HW_DONGLE_SJA:
		case HW_DONGLE_SJA_EPP:
		  local.dwBase      = dev->port.dng.dwPort;
      local.wIrqLevel   = dev->port.dng.wIrq;
			break;
		case HW_PCI:
		  local.dwBase      = dev->port.pci.dwPort;
      local.wIrqLevel   = dev->port.pci.wIrq;
      break;
    case HW_USB:
      #ifdef USB_SUPPORT
      if (pcan_usb_getSerialNumber(dev))
        local.dwBase    = 0;
      else
        local.dwBase    = dev->port.usb.dwSerialNumber;
        local.wIrqLevel = dev->port.usb.ucHardcodedDevNr;
      #endif
			break;
	}
  local.dwReadCounter   = dev->readFifo.dwTotal;
  local.dwWriteCounter  = dev->writeFifo.dwTotal;
  local.dwIRQcounter    = dev->dwInterruptCounter;
  local.dwErrorCounter  = dev->dwErrorCounter;
  local.wErrorFlag      = dev->wCANStatus;
  
	// get infos for friends of polling operation
  if (!pcan_fifo_empty(&dev->readFifo))
    local.wErrorFlag |= CAN_ERR_QRCVEMPTY;
    
  if (!pcan_fifo_near_full(&dev->writeFifo))
  	local.wErrorFlag |= CAN_ERR_QXMTFULL;
	
  local.nLastError      = dev->nLastError;
  local.nOpenPaths      = dev->nOpenPaths;

	strncpy(local.szVersionString, pcan_drv.szVersionString, VERSIONSTRING_LEN);

	if (copy_to_user(diag, &local, sizeof(local)))
		err = -EFAULT;
	
	return err;
}

//----------------------------------------------------------------------------
// is called at user ioctl() with cmd = PCAN_INIT 
int pcan_ioctl_init(struct pcandev *dev, TPCANInit *Init)
{
  int err = 0;
	TPCANInit local;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_INIT)\n", DEVICE_NAME);

	if (copy_from_user(&local, Init, sizeof(*Init)))
	{
		err = -EFAULT;
		goto fail;
	}
	
  // flush fifo contents
  err = pcan_fifo_reset(&dev->writeFifo);
  if (err)
    goto fail;

  err = pcan_fifo_reset(&dev->readFifo);
  if (err)
    goto fail;

  // wait until fifo is empty or MAX_WAIT_UNTIL_CLOSE time is elapsed
  wait_until_fifo_empty(dev, MAX_WAIT_UNTIL_CLOSE);

  // release the device 
  dev->device_release(dev);

  // init again
  err = dev->device_open(dev, local.wBTR0BTR1, local.ucCANMsgType, local.ucListenOnly);
  
  if (!err)
  {
  	dev->wBTR0BTR1    = local.wBTR0BTR1;
	  dev->ucCANMsgType = local.ucCANMsgType;
	  dev->ucListenOnly = local.ucListenOnly;
	}
    
  fail:
  return err;
}


//----------------------------------------------------------------------------
// get BTR0BTR1 init values
static int pcan_ioctl_BTR0BTR1(struct pcandev *dev, TPBTR0BTR1 *BTR0BTR1)
{
  int err = 0;
	TPBTR0BTR1 local;
	
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl(PCAN_BTR0BTR1)\n", DEVICE_NAME);
  
	if (copy_from_user(&local, BTR0BTR1, sizeof(local)))
	{
		err = -EFAULT;
		goto fail;
	}
	
  // this does not influence hardware settings, only BTR0BTR1 values are calculated
	local.wBTR0BTR1 = sja1000_bitrate(local.dwBitRate);
	
	if (!local.wBTR0BTR1)
	{
		err = -EFAULT;
		goto fail;
	}	
	
	if (copy_to_user(BTR0BTR1, &local, sizeof(*BTR0BTR1)))
		err = -EFAULT;
 
  fail:
  return err;
}
 

//----------------------------------------------------------------------------
// is called at user ioctl() call 
static int pcan_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg)
{
  int err;
  struct fileobj *fobj = (struct fileobj *)filep->private_data;
  struct pcandev *dev  = fobj->dev;

  // if the device is plugged out
  if (!dev->ucPhysicallyInstalled)
    return -ENODEV;

  switch(cmd)
  {
    case PCAN_READ_MSG:
      err = pcan_ioctl_read(filep, dev, (TPCANRdMsg *)arg); // support blocking and nonblocking IO
      break;
    case PCAN_WRITE_MSG:
      err = pcan_ioctl_write(filep, dev, (TPCANMsg *)arg);  // support blocking and nonblocking IO
      break;
    case PCAN_GET_EXT_STATUS:
      err = pcan_ioctl_extended_status(dev, (TPEXTENDEDSTATUS *)arg);
      break;
    case PCAN_GET_STATUS:
      err = pcan_ioctl_status(dev, (TPSTATUS *)arg);
      break;
    case PCAN_DIAG:
      err = pcan_ioctl_diag(dev, (TPDIAG *)arg);
      break;
    case PCAN_INIT: 
      err = pcan_ioctl_init(dev, (TPCANInit *)arg);	  
      break;
    case PCAN_BTR0BTR1:
      err = pcan_ioctl_BTR0BTR1(dev, (TPBTR0BTR1 *)arg);
      break;
    
    default: 
      DPRINTK(KERN_DEBUG "%s: pcan_ioctl(%d)\n", DEVICE_NAME, cmd);	
      err = -ENOTTY;
      break;
  }
  
  DPRINTK(KERN_DEBUG "%s: pcan_ioctl() = %d\n", DEVICE_NAME, err);
  return err;
}

//----------------------------------------------------------------------------
// is called when read from the path
static ssize_t pcan_read(struct file *filep, char *buf, size_t count, loff_t *f_pos)
{
  int    err;
  struct fileobj *fobj = (struct fileobj *)filep->private_data;
  struct pcandev *dev  = fobj->dev; 
  int    len = 0;
  TPCANRdMsg *m;
  TPCANRdMsg Message;

  // DPRINTK(KERN_DEBUG "%s: pcan_read().\n", DEVICE_NAME);
  
  // if the device is plugged out
  if (!dev->ucPhysicallyInstalled)
    return -ENODEV;

  if (fobj->nReadRest <= 0)
  {
    // support nonblocking read if requested
    if ((filep->f_flags & O_NONBLOCK) && (!pcan_fifo_empty(&dev->readFifo)))
      return -EAGAIN;

    // sleep until data are available
    err = wait_event_interruptible(dev->read_queue, (pcan_fifo_empty(&dev->readFifo)));
    if (err)
      return err;

    // if the device is plugged out
    if (!dev->ucPhysicallyInstalled)
      return -ENODEV;

    // nothing in buffer, get new data out of fifo
		err = pcan_fifo_claim_for_get(&dev->readFifo, (void *)&m);
		if (err)
		  return err;

		// signal a pending status
		if (dev->wCANStatus)
			m->Msg.MSGTYPE |= MSGTYPE_STATUS;
			    
		memcpy(&Message, m, sizeof(*m));
		
		// free fifo
		err = pcan_fifo_get(&dev->readFifo);
		if (err)
			return err;
			
    fobj->nReadRest = pcan_make_output(fobj->pcReadBuffer, &Message);
    fobj->pcReadPointer = fobj->pcReadBuffer;
  }
  
  // give the data to the user
  if (count > fobj->nReadRest)
  {
    // put all data to user
    len = fobj->nReadRest;
    fobj->nReadRest = 0;
    if (copy_to_user(buf, fobj->pcReadPointer, len))
      return -EFAULT;
    fobj->pcReadPointer = fobj->pcReadBuffer; 
  }
  else
  {
    // put only partial data to user
    len = count;
    fobj->nReadRest -= count;
    if (copy_to_user(buf, fobj->pcReadPointer, len))
      return -EFAULT;
    fobj->pcReadPointer = (u8 *)((u8*)fobj->pcReadPointer + len);
  }
  
  *f_pos += len;
  fobj->nTotalReadCount += len;

  // DPRINTK(KERN_DEBUG "%s: pcan_read() is OK\n", DEVICE_NAME);
  
  return len;
}

//----------------------------------------------------------------------------
// is called when written to the path
static ssize_t pcan_write(struct file *filep, const char *buf, size_t count, loff_t *f_pos)
{
  struct fileobj *fobj = (struct fileobj *)filep->private_data;
  struct pcandev *dev  = fobj->dev; 
  int err = 0; 
  u32 dwRest;
  u8 *ptr;
  TPCANInit Init;
  TPCANMsg Message;
    
  // DPRINTK(KERN_DEBUG "%s: pcan_write().\n", DEVICE_NAME);
   
  // if the device is plugged out
  if (!dev->ucPhysicallyInstalled)
    return -ENODEV;

  // calculate remaining buffer space
  dwRest = WRITEBUFFER_SIZE - (fobj->pcWritePointer - fobj->pcWriteBuffer); // nRest > 0!
  count  = (count > dwRest) ? dwRest : count;
  
  if (copy_from_user(fobj->pcWritePointer, buf, count))
    return -EFAULT;

  // adjust working pointer to end
  fobj->pcWritePointer += count;
  
  // iterate search blocks ending with '\n'
  while (1)
  {
    // search first '\n' from begin of buffer
    ptr = fobj->pcWriteBuffer; 
    while ((*ptr != '\n') && (ptr < fobj->pcWritePointer))
      ptr++;
  
    // parse input when a CR was found
    if ((*ptr == '\n') && (ptr < fobj->pcWritePointer))
    {
      u32 amount = (u32)(fobj->pcWritePointer - ptr - 1);
      u32 offset = (u32)(ptr - fobj->pcWriteBuffer + 1);
            
	    if ((amount > WRITEBUFFER_SIZE) || (offset > WRITEBUFFER_SIZE))
	    {
        printk(KERN_ERR "%s: fault in pcan_write() %u %u, %u: \n", DEVICE_NAME, count, amount, offset);
	      return -EFAULT;
	    }
  
      if (pcan_parse_input_idle(fobj->pcWriteBuffer))
      {
        if (pcan_parse_input_message(fobj->pcWriteBuffer, &Message))
        {
	        if ((err = pcan_parse_input_init(fobj->pcWriteBuffer, &Init)))
            return err;
	        else
		      {
		        #if 0
	          DPRINTK(KERN_DEBUG "%s: ***** Init 0x%04x 0x%02x 0x%02x\n", DEVICE_NAME, 
		          Init.wBTR0BTR1, Init.ucCANMsgType, Init.ucListenOnly);
			      #endif
			  
			      // init the associated chip and the fifos again with new parameters
			      err = dev->device_open(dev, Init.wBTR0BTR1, Init.ucCANMsgType, Init.ucListenOnly);
			      if (err)
			        return err;
						else
						{
							dev->wBTR0BTR1    = Init.wBTR0BTR1;
							dev->ucCANMsgType = Init.ucCANMsgType;
							dev->ucListenOnly = Init.ucListenOnly;
						}
							
			      err = pcan_fifo_reset(&dev->writeFifo);
			      if (err)
			        return err;
				
			      err = pcan_fifo_reset(&dev->readFifo);
			      if (err)
			        return err;
			    }
	      }
	      else
	      {
	        TPCANMsg *m;
		
		      #if 0 // ------- print out message, begin -----------
	        int i = 0;
		
	        DPRINTK(KERN_DEBUG "%s: *** 0x%08x 0x%02x %d . ",
	          DEVICE_NAME, Message.ID, Message.MSGTYPE, Message.LEN);
	      
	        while (i++ < Message.LEN)
	          DPRINTK(KERN_DEBUG "0x%02x ", Message.DATA[i]);
	      
		      DPRINTK(KERN_DEBUG " ***\n");  
		      #endif // ------- print out message, end ------------
		      
          // support nonblocking write if requested
          if ((filep->f_flags & O_NONBLOCK) && (!pcan_fifo_near_full(&dev->writeFifo)) && (!atomic_read(&dev->DataSendReady)))
            return -EAGAIN;
		      
          // sleep until space is available
          err = wait_event_interruptible(dev->write_queue, 
	                   (pcan_fifo_near_full(&dev->writeFifo) || atomic_read(&dev->DataSendReady)));
          if (err)
            return err;
	    
          // if the device is plugged out
          if (!dev->ucPhysicallyInstalled)
            return -ENODEV;

	        // filter extended data if initialized to standard only
		      if (!(dev->bExtended) && ((Message.MSGTYPE & MSGTYPE_EXTENDED) || (Message.ID > 2047)))
		        return -EINVAL;
      
		      // put message into write FIFO
		      err = pcan_fifo_claim_for_put(&dev->writeFifo, (void *)&m);
		      if (err)
		        return err;
			
			    memcpy(m, &Message, sizeof(*m));
			    
			    err = pcan_fifo_put(&dev->writeFifo);
			    if (err)
			      return err;
			      
			    // pull new transmission
			    if (atomic_read(&dev->DataSendReady))
			    {
  			    atomic_set(&dev->DataSendReady, 0);
			      if ((err = dev->device_write(dev)))
			      {
  			      atomic_set(&dev->DataSendReady, 1);
			        return err;
			      }
			    }
			    else
          {
			      // DPRINTK(KERN_DEBUG "%s: pushed %d item into Fifo\n", DEVICE_NAME, dev->writeFifo.nStored);
          }
		    }
		  }
   
      // move rest of amount data in buffer offset steps to left
      memmove(fobj->pcWriteBuffer, ptr + 1, amount);
      fobj->pcWritePointer -= offset; 
    }
    else
      break; // no CR found
  }
  
  if (fobj->pcWritePointer >= (fobj->pcWriteBuffer + WRITEBUFFER_SIZE))
  {
    fobj->pcWritePointer = fobj->pcWriteBuffer; // reject all
    return -EFAULT;
  }
  
  // DPRINTK(KERN_DEBUG "%s: pcan_write() is OK\n", DEVICE_NAME);
  
  return count;
}

//----------------------------------------------------------------------------
// is called at poll or select
static unsigned int pcan_poll(struct file *filep, poll_table *wait)
{
  struct fileobj *fobj = (struct fileobj *)filep->private_data;
  struct pcandev *dev  = fobj->dev;
  unsigned int mask = 0;
  
  poll_wait(filep, &dev->read_queue,  wait);
  poll_wait(filep, &dev->write_queue, wait);
  
  // return on ops that could be performed without blocking
  if (pcan_fifo_empty(&dev->readFifo))      mask |= (POLLIN  | POLLRDNORM);
  if (pcan_fifo_near_full(&dev->writeFifo)) mask |= (POLLOUT | POLLWRNORM);

  return mask; 
}

//****************************************************************************
// GLOBALS

//----------------------------------------------------------------------------
// this structure is used in init_module(void)
struct file_operations pcan_fops =
{
  // marrs:  added owner, which is used to implement a use count that disallows
  //         rmmod calls when the driver is still in use (as suggested by
  //         Duncan Sands on the linux-kernel mailinglist)
  
  #if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,14)
  owner:      THIS_MODULE,
  #endif
  open:       pcan_open,
  release:    pcan_release,
  read:       pcan_read,
  write:      pcan_write,
  ioctl:      pcan_ioctl,
  poll:       pcan_poll,
};

// end of file

